// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/events/ozone/device/udev/device_manager_udev.h"

#include <stddef.h>

#include "base/macros.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "ui/events/ozone/device/device_event.h"
#include "ui/events/ozone/device/device_event_observer.h"

namespace ui {

namespace {

    const char* const kSubsystems[] = {
        "input",
        "drm",
    };

    // Severity levels from syslog.h. We can't include it directly as it
    // conflicts with base/logging.h
    enum {
        SYS_LOG_EMERG = 0,
        SYS_LOG_ALERT = 1,
        SYS_LOG_CRIT = 2,
        SYS_LOG_ERR = 3,
        SYS_LOG_WARNING = 4,
        SYS_LOG_NOTICE = 5,
        SYS_LOG_INFO = 6,
        SYS_LOG_DEBUG = 7,
    };

    // Log handler for messages generated from libudev.
    void UdevLog(struct udev* udev,
        int priority,
        const char* file,
        int line,
        const char* fn,
        const char* format,
        va_list args)
    {
        if (priority <= SYS_LOG_ERR)
            LOG(ERROR) << "libudev: " << fn << ": " << base::StringPrintV(format, args);
        else if (priority <= SYS_LOG_INFO)
            VLOG(1) << "libudev: " << fn << ": " << base::StringPrintV(format, args);
        else // SYS_LOG_DEBUG
            VLOG(2) << "libudev: " << fn << ": " << base::StringPrintV(format, args);
    }

    // Create libudev context.
    device::ScopedUdevPtr UdevCreate()
    {
        struct udev* udev = device::udev_new();
        if (udev) {
            device::udev_set_log_fn(udev, UdevLog);
            device::udev_set_log_priority(udev, SYS_LOG_DEBUG);
        }
        return device::ScopedUdevPtr(udev);
    }

    // Start monitoring input device changes.
    device::ScopedUdevMonitorPtr UdevCreateMonitor(struct udev* udev)
    {
        struct udev_monitor* monitor = device::udev_monitor_new_from_netlink(udev, "udev");
        if (monitor) {
            for (size_t i = 0; i < arraysize(kSubsystems); ++i)
                device::udev_monitor_filter_add_match_subsystem_devtype(
                    monitor, kSubsystems[i], NULL);

            if (device::udev_monitor_enable_receiving(monitor))
                LOG(ERROR) << "Failed to start receiving events from udev";
        } else {
            LOG(ERROR) << "Failed to create udev monitor";
        }

        return device::ScopedUdevMonitorPtr(monitor);
    }

} // namespace

DeviceManagerUdev::DeviceManagerUdev()
    : udev_(UdevCreate())
{
}

DeviceManagerUdev::~DeviceManagerUdev()
{
}

void DeviceManagerUdev::CreateMonitor()
{
    if (monitor_)
        return;
    monitor_ = UdevCreateMonitor(udev_.get());
    if (monitor_) {
        int fd = device::udev_monitor_get_fd(monitor_.get());
        CHECK_GT(fd, 0);
        base::MessageLoopForUI::current()->WatchFileDescriptor(
            fd, true, base::MessagePumpLibevent::WATCH_READ, &controller_, this);
    }
}

void DeviceManagerUdev::ScanDevices(DeviceEventObserver* observer)
{
    CreateMonitor();

    device::ScopedUdevEnumeratePtr enumerate(
        device::udev_enumerate_new(udev_.get()));
    if (!enumerate)
        return;

    for (size_t i = 0; i < arraysize(kSubsystems); ++i)
        device::udev_enumerate_add_match_subsystem(enumerate.get(), kSubsystems[i]);
    device::udev_enumerate_scan_devices(enumerate.get());

    struct udev_list_entry* devices = device::udev_enumerate_get_list_entry(enumerate.get());
    struct udev_list_entry* entry;

    udev_list_entry_foreach(entry, devices)
    {
        device::ScopedUdevDevicePtr device(device::udev_device_new_from_syspath(
            udev_.get(), device::udev_list_entry_get_name(entry)));
        if (!device)
            continue;

        scoped_ptr<DeviceEvent> event = ProcessMessage(device.get());
        if (event)
            observer->OnDeviceEvent(*event.get());
    }
}

void DeviceManagerUdev::AddObserver(DeviceEventObserver* observer)
{
    observers_.AddObserver(observer);
}

void DeviceManagerUdev::RemoveObserver(DeviceEventObserver* observer)
{
    observers_.RemoveObserver(observer);
}

void DeviceManagerUdev::OnFileCanReadWithoutBlocking(int fd)
{
    // The netlink socket should never become disconnected. There's no need
    // to handle broken connections here.
    TRACE_EVENT1("evdev", "UdevDeviceChange", "socket", fd);

    device::ScopedUdevDevicePtr device(
        device::udev_monitor_receive_device(monitor_.get()));
    if (!device)
        return;

    scoped_ptr<DeviceEvent> event = ProcessMessage(device.get());
    if (event)
        FOR_EACH_OBSERVER(
            DeviceEventObserver, observers_, OnDeviceEvent(*event.get()));
}

void DeviceManagerUdev::OnFileCanWriteWithoutBlocking(int fd)
{
    NOTREACHED();
}

scoped_ptr<DeviceEvent> DeviceManagerUdev::ProcessMessage(udev_device* device)
{
    const char* path = device::udev_device_get_devnode(device);
    const char* action = device::udev_device_get_action(device);
    const char* subsystem = device::udev_device_get_property_value(device, "SUBSYSTEM");

    if (!path || !subsystem)
        return nullptr;

    DeviceEvent::DeviceType device_type;
    if (!strcmp(subsystem, "input") && base::StartsWith(path, "/dev/input/event", base::CompareCase::SENSITIVE))
        device_type = DeviceEvent::INPUT;
    else if (!strcmp(subsystem, "drm") && base::StartsWith(path, "/dev/dri/card", base::CompareCase::SENSITIVE))
        device_type = DeviceEvent::DISPLAY;
    else
        return nullptr;

    DeviceEvent::ActionType action_type;
    if (!action || !strcmp(action, "add"))
        action_type = DeviceEvent::ADD;
    else if (!strcmp(action, "remove"))
        action_type = DeviceEvent::REMOVE;
    else if (!strcmp(action, "change"))
        action_type = DeviceEvent::CHANGE;
    else
        return nullptr;

    return make_scoped_ptr(
        new DeviceEvent(device_type, action_type, base::FilePath(path)));
}

} // namespace ui
